/*
 * Copyright (c) 2008-2023, Hazelcast, Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.hazelcast.huaweicloud;

import com.hazelcast.internal.json.Json;
import com.hazelcast.internal.json.JsonArray;
import com.hazelcast.internal.json.JsonObject;
import com.hazelcast.internal.json.JsonValue;
import com.hazelcast.internal.util.CollectionUtil;
import com.hazelcast.internal.util.StringUtil;
import com.hazelcast.logging.ILogger;
import com.hazelcast.logging.Logger;
import com.huaweicloud.sdk.core.auth.AKSKSigner;
import com.huaweicloud.sdk.core.auth.BasicCredentials;
import com.huaweicloud.sdk.core.exception.ConnectionException;
import com.huaweicloud.sdk.core.http.HttpRequest;
import okhttp3.Headers;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

import java.io.IOException;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.HashSet;
import java.util.Collections;

//1.https://support.huaweicloud.com/api-ecs/ecs_02_0104.html#section4
//Responsible for connecting to ECS API
class HwApi {
    private static final ILogger LOGGER = Logger.getLogger(HwApi.class);
    private static final String API_VERSION = "v1";
    private static final String ERROR = "APIGW.0301";
    private static final String ECS_API = "https://ecs.%s.myhuaweicloud.com";
    private String endPoint;
    private final Set<Tag> tagSet = new HashSet<>();

    HwApi() {
        this(ECS_API);
    }

    /**
     * For test purposes only.
     */
    HwApi(String endPoint) {
        this.endPoint = endPoint;
    }

    // get instances from client,this tags can be null
    public Map<String, String> describeInstances(Map<String, String> instanceMap, List<Tag> tags) {
        String zone = instanceMap.get("zone");
        String projectId = instanceMap.get("projectId");
        String serverId = instanceMap.get("serverId");
        // get url for API connection
        String url = urlForIpList(zone, projectId, serverId);

        if (instanceMap.containsKey("accessKey") || instanceMap.containsKey("access")) {
            LOGGER.info("get ip by ak and sk");
            return describeInstancesByAkAndSk(url, instanceMap, tags);
        } else {
            LOGGER.info("get ip by Iam");
            return describeInstancesByIam(url, instanceMap.get("accessToken"), tags);
        }

    }

    private Map<String, String> describeInstancesByAkAndSk(String url, Map<String, String> instanceMap,
            List<Tag> tags) {
        BasicCredentials credential = new BasicCredentials();
        credential.setAk(instanceMap.get("accessKey"));
        credential.setSk(instanceMap.get("secretKey"));
        HttpRequest httpRequest = HttpRequest.newBuilder().withEndpoint(url).build();
        Map<String, String> headers = createHeaders(httpRequest, credential);
        // AK and SK from metadata
        String accessToken = instanceMap.get("accessToken");
        if (!StringUtil.isNullOrEmpty(accessToken)) {
            headers.put("X-Security-Token", accessToken);
        }
        return getIpMap(tags, headers, url);

    }

    private Map<String, String> describeInstancesByIam(String url, String accessToken, List<Tag> tags) {
        if (StringUtil.isNullOrEmpty(accessToken)) {
            LOGGER.warning("The accessToken is invalid");
            return Collections.emptyMap();
        }

        return getIpMap(tags, createHeaders(accessToken), url);
    }

    private Map<String, String> getIpMap(List<Tag> tags, Map<String, String> headers, String url) {
        OkHttpClient client = new OkHttpClient();

        Request request = new Request.Builder().url(url).headers(Headers.of(headers)).build();
        String responseData = null;
        Map<String, String> interfaces = new HashMap<>();
        // call http
        try (Response response = client.newCall(request).execute()) {
            if (response.body() != null) {
                responseData = response.body().string();
            } else {
                return interfaces;
            }
        } catch (IOException e) {
            LOGGER.warning("connection error" + e);
        }

        JsonObject object = Json.parse(responseData).asObject();
        String code = object.getString("error_code", "null");
        if (ERROR.equals(code)) {
            throw new ConnectionException("connect failed,please make sure your ak and sk is true");
        }
        JsonObject interfaceAttachment = object.get("server").asObject();
        JsonArray tagsArr = interfaceAttachment.get("tags").asArray();

        if (!checkTags(tags, tagsArr)) {
            return interfaces;
        }

        for (JsonObject.Member member : interfaceAttachment.get("addresses").asObject()) {
            JsonArray addArr = toJsonArray(member.getValue());
            for (JsonValue ip : addArr) {
                JsonObject members = ip.asObject();
                if (StringUtil.isNullOrEmptyAfterTrim(String.valueOf(members.get("addr")))) {
                    continue;
                }
                if ("fixed".equals(members.get("OS-EXT-IPS:type").asString())) {
                    interfaces.put("privateIp", members.get("addr").asString());
                } else {
                    interfaces.put("publicIp", members.get("addr").asString());
                }
            }
        }
        return interfaces;
    }

    private boolean checkTags(List<Tag> tags, JsonArray tagsArr) {
        for (int i = 0; i < tagsArr.size(); i++) {
            String tagStr = tagsArr.get(i).asString();
            String key;
            String value;
            if (tagStr.contains("=")) {
                key = tagStr.split("=")[0];
                value = tagStr.split("=")[1];
            } else {
                key = tagStr;
                value = "";
            }
            tagSet.add(new Tag(key, value));
        }
        if (CollectionUtil.isNotEmpty(tags)) {
            // if no server found,return empty map
            for (Tag tag : tags) {
                if (!tagSet.contains(tag)) {
                    LOGGER.warning("No IP addresses found!Retry your tags");
                    return false;
                }
            }
        }
        return true;
    }

    private static JsonArray toJsonArray(JsonValue jsonValue) {
        if (jsonValue == null || jsonValue.isNull()) {
            return new JsonArray();
        } else {
            return jsonValue.asArray();
        }
    }

    // use huaweicloud sdk to sign the request header
    private Map<String, String> createHeaders(HttpRequest httpRequest, BasicCredentials credential) {
        return AKSKSigner.sign(httpRequest, credential);
    }

    // create request header with token
    private Map<String, String> createHeaders(String token) {
        Map<String, String> headers = new HashMap<>();
        headers.put("X-Auth-Token", token);
        headers.put("Content-Type", "application/json");
        return headers;
    }

    // create url for api
    private String urlForIpList(String zone, String projectId, String serverId) {
        endPoint = String.format(endPoint, zone);
        return String.join("/", endPoint, API_VERSION, projectId, "cloudservers", serverId);
    }

}
